package com.sky.service.impl;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sky.constant.*;
import com.sky.context.BaseContext;
import com.sky.dto.EmployeeDTO;
import com.sky.dto.EmployeeLoginDTO;
import com.sky.dto.EmployeePageQueryDTO;
import com.sky.dto.PasswordEditDTO;
import com.sky.entity.Employee;
import com.sky.exception.AccountDisabledException;
import com.sky.exception.AccountNotFoundException;
import com.sky.exception.PasswordErrorException;
import com.sky.mapper.EmployeeMapper;
import com.sky.properties.JwtProperties;
import com.sky.result.PageBean;
import com.sky.service.EmployeeService;
import com.sky.utils.JwtUtil;
import com.sky.vo.EmployeeLoginVO;
import lombok.extern.slf4j.Slf4j;
import org.mindrot.jbcrypt.BCrypt;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class EmployeeServiceImpl implements EmployeeService {

    @Resource
    private EmployeeMapper employeeMapper;

    @Resource
    private JwtProperties jwtProperties;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private HttpServletResponse httpServletResponse;

    /**
     * 登录
     *
     * @param employeeLoginDTO dto
     * @return vo
     */
    @Override
    public EmployeeLoginVO login(EmployeeLoginDTO employeeLoginDTO) {
        // 根据用户输入的用户名查询数据库
        Employee employee = employeeMapper.selectByUsername(employeeLoginDTO.getUsername());
        // 判断账号是否存在
        if (employee == null) {
            throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
        }
        // 从Redis判断账户是否被锁定(redis中是否存在对应的键）
        Boolean result = redisTemplate.hasKey(RedisConstant.ACCOUNT_LOCK + employeeLoginDTO.getUsername());
        if (result) {
            // 账号被锁定
            Long expire = redisTemplate.getExpire(RedisConstant.ACCOUNT_LOCK + employeeLoginDTO.getUsername(), TimeUnit.MINUTES); // 获取锁的剩余时间
            throw new AccountNotFoundException(String.format(MessageConstant.ACCOUNT_LOCKED, expire));
        }
        // 判断密码是否正确  使用 Bcrypt 加密
        boolean checkpassword = BCrypt.checkpw(employeeLoginDTO.getPassword(), employee.getPassword());
        // 创建Redis字符串类型的数据

        // 账号错误次数的RedisKey
        String accountErrorCountRedisKey = RedisConstant.ACCOUNT_PASSWORD_ERROR_COUNT + employeeLoginDTO.getUsername();
        // 账号冻结的RedisKey
        String accountLockedRedisKey = RedisConstant.ACCOUNT_LOCK + employeeLoginDTO.getUsername();
        // 获取键的值
        if (!checkpassword) {
            // 密码错误
            // 如果不存在这个键就创建一个初始值为0(代表错误次数),60秒期限的String类型数据
            redisTemplate.opsForValue().setIfAbsent(accountErrorCountRedisKey, 0, 60, TimeUnit.SECONDS);
            // 获取 accountErrorCountRedisKey 的值,即错误次数
            Integer count = (Integer) redisTemplate.opsForValue().get(accountErrorCountRedisKey);
            // 把当前用户账号的accountErrorCountRedisKey的值（错误次数）加一
            if (count != null) {
                redisTemplate.opsForValue().set(accountErrorCountRedisKey, count + 1, 0);
                if (count == 2) {
                    // 账号锁定60分钟
                    redisTemplate.opsForValue().set(accountLockedRedisKey, 1, 60, TimeUnit.MINUTES);
                    redisTemplate.delete(accountErrorCountRedisKey);
                }
                // 一分钟内密码错误3次,锁定账号一个小时
                throw new PasswordErrorException(String.format(MessageConstant.ACCOUNT_Times, 2 - count));
            }
        }

        // 判断账号是否被禁用
        if (employee.getStatus().equals(StatusConstant.DISABLE)) {
            throw new AccountDisabledException(MessageConstant.ACCOUNT_DISABLED);
        }

        // 账号密码正确就生成jwt令牌，令牌中存放用户的信息为：id和用户名
        Map<String, Object> map = new HashMap<>();
        map.put(JwtClaimsConstant.EMP_ID, employee.getId());
        map.put(JwtClaimsConstant.USERNAME, employee.getUsername());

        String token = JwtUtil.createJWT(
                jwtProperties.getAdminSecretKey(),
                jwtProperties.getAdminTtl(),
                map);

        // 当前用户的id
        Long id = employeeMapper.selectByUsername(employeeLoginDTO.getUsername()).getId();
        // 把token存入redis中去 这里实现用户长时间不操作令牌就会过期（拦截器中实现）
        redisTemplate.opsForValue().setIfAbsent(RedisConstant.ADMIN_ID + id, token, 5, TimeUnit.MINUTES);

        // 返回employeeLoginVO对象
        return EmployeeLoginVO.builder()
                .id(employee.getId())
                .userName(employee.getName())
                .name(employee.getName())
                .token(token)
                .build();
    }

    /**
     * 新增员工
     *
     * @param employeeDTO dto 员工信息
     */
    @Override
    public void add(EmployeeDTO employeeDTO) {
        Employee employee = new Employee();
        BeanUtils.copyProperties(employeeDTO, employee); // dto中的属性和employee中的数据对应
        // 额外处理特殊属性
        employee.setPassword(BCrypt.hashpw(PasswordConstant.DEFAULT_PASSWORD, BCrypt.gensalt())); // 密码使用 Bcrypt 加密
        employee.setStatus(StatusConstant.ENABLE);
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
        // 从当前本地线程取数据
        employee.setCreateUser(BaseContext.getCurrentId());
        employee.setUpdateUser(BaseContext.getCurrentId());
        // 保存数据
        employeeMapper.add(employee);
    }

    /**
     * 员工条件分页查询
     *
     * @param employeePageQueryDTO dto
     * @return pagebean
     */
    @Override
    public PageBean pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
        PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());
        Page<Employee> employees = employeeMapper.pageQuery(employeePageQueryDTO);
        return new PageBean(employees.getTotal(), employees.getResult());
    }

    /**
     * 修改员工状态（启用or禁用）
     *
     * @param status 状态
     * @param id     员工id
     */
    @Override
    public void changeStatus(Integer status, Long id) {
        Employee employee = Employee.builder()
                .status(status)
                .id(id)
                .updateTime(LocalDateTime.now())
                .updateUser(BaseContext.getCurrentId()).build();
        employeeMapper.update(employee);
    }

    /**
     * 根据id查询员工
     *
     * @param id 员工id
     * @return employee
     */
    @Override
    public Employee getById(Long id) {
        return employeeMapper.getById(id);
    }

    /**
     * 编辑员工信息
     *
     * @param employeeDTO dto
     */
    @Override
    public void update(EmployeeDTO employeeDTO) {
        Employee employee = Employee.builder()
                .updateTime(LocalDateTime.now())
                .updateUser(BaseContext.getCurrentId()).build();
        BeanUtils.copyProperties(employeeDTO, employee);
        employeeMapper.update(employee);
    }

    /**
     * 退出系统
     */
    @Override
    public void logout() {
        httpServletResponse.setHeader("Token", null);
    }

    /**
     * 修改密码
     *
     * @param passwordEditDTO dto
     */
    @Override
    public void editPassword(PasswordEditDTO passwordEditDTO) {
        // 先判断旧密码是否正确
        passwordEditDTO.setEmpId(BaseContext.getCurrentId());
        Employee employee = employeeMapper.getById(passwordEditDTO.getEmpId());
        String password = employee.getPassword();
        boolean checkpw = BCrypt.checkpw(passwordEditDTO.getOldPassword(), password);
        if (!checkpw) {
            throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
        }
        // 然后新密码加密
        String newPassword = BCrypt.hashpw(passwordEditDTO.getNewPassword(), BCrypt.gensalt());
        employeeMapper.editPassword(passwordEditDTO.getEmpId(), newPassword);
        // 最后返回状态码401（前端约定好，跳转到登录页面）
        httpServletResponse.setStatus(401);
    }
}
